﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.IO;

namespace ComicsEditor
{
    public class Balloon : EditableObject
    {
        private Contour contour;

        private double curveSmooth = Math.PI*3/4;
        private int curveStep = 10;

        private GraphicsPath path = null;
        private Brush fill = new SolidBrush(Color.White);
        private Pen stroke = new Pen(Color.Black, 1.0f);

        public Balloon(Page Page, Contour contour) : base(Page)
        {
            this.contour = contour;
        }

        public Balloon(Page Page, GraphicsPath path, Brush fill, Pen stroke) : base(Page)
        {
            this.path = path;
            this.fill = fill;
            this.stroke = stroke;
        }

        public override void BeginEdit()
        {
            base.BeginEdit();
            Page.Changed = true;
        }

        public override void EndEdit()
        {
            base.EndEdit();
            Page.Changed = true;
        }

        public double CurveSmooth
        {
            get
            {
                return curveSmooth;
            }
            set
            {
                curveSmooth = value;
                path = null;
                Page.Changed = true;
            }
        }

        public int CurveStep
        {
            get
            {
                return curveStep;
            }
            set
            {
                curveStep = value;
                path = null;
                Page.Changed = true;
            }
        }

        public Brush Fill
        {
            get
            {
                return fill;
            }
            set
            {
                fill = value;
                Page.Changed = true;
            }
        }

        public Pen Stroke
        {
            get
            {
                return stroke;
            }
            set
            {
                stroke = value;
                Page.Changed = true;
            }
        }

        public bool IsReadOnly
        {
            get
            {
                return contour == null;
            }
        }

        public bool Contains(Point p)
        {
            if (contour != null)
                return contour.Contains(p.X, p.Y);
            if (path != null)
                return path.IsVisible(p);
            return false;
        }

        public void Translate(int dx, int dy)
        {
            if (contour != null)
                contour.Translate(dx, dy);
            if (path != null)
            {
                Matrix m = new Matrix();
                m.Translate(dx, dy);
                path.Transform(m);
            }
        }

        public override void Draw(Graphics Graphics)
        {
            if (path == null)
                Rebuild();

            Graphics.Transform = Page.GetTransform(Point.Empty);
            if (fill != null)
                Graphics.FillPath(fill, path);
            if (stroke != null)
                Graphics.DrawPath(stroke, path);
            Graphics.Transform = new Matrix();

            if (Editing)
            {
                GraphicsPath pathTransformed = (GraphicsPath)path.Clone();
                pathTransformed.Transform(Page.GetTransform(Point.Empty));
                Graphics.DrawPath(DashedLine.penEven, pathTransformed);
                Graphics.DrawPath(DashedLine.penOdd, pathTransformed);
            }
        }

        public void Rebuild()
        {
            int i, j;

            double[] d = new double[contour.Count];
            for (i = 0; i < contour.Count; i++)
            {
                Point a = contour[(i + 5)%contour.Count];
                Point b = contour[(i - 5 + contour.Count)%contour.Count];
                double ax = a.X - contour[i].X;
                double ay = a.Y - contour[i].Y;
                double bx = b.X - contour[i].X;
                double by = b.Y - contour[i].Y;
                double ad = Math.Sqrt(ax*ax + ay*ay);
                double bd = Math.Sqrt(bx*bx + by*by);
                double dot = (ax*bx + ay*by)/ad/bd;
                if (dot < -1)
                    dot = -1;
                d[i] = Math.Acos(dot);
            }

            List<int> critical = new List<int>();
            int min = int.MaxValue;
            for (i = -5; i < 0; i++)
                if (d[(i + d.Length)%d.Length] < curveSmooth && (min == int.MaxValue || d[(i + d.Length)%d.Length] < d[(min + d.Length)%d.Length]))
                    min = i;
            for (i = 0; i < d.Length; i++)
            {
                if (min != int.MaxValue && i - min > 5)
                {
                    critical.Add((min + d.Length)%d.Length);
                    min = int.MaxValue;
                }
                if (d[i] < curveSmooth && (min == int.MaxValue || d[i] < d[(min + d.Length)%d.Length]))
                    min = i;
            }

            path = new GraphicsPath();

            if (critical.Count == 0)
                critical.Add(0);

            for (i = 0; i < critical.Count; i++)
            {
                List<Point> curvePoints = new List<Point>();
                int begin = critical[i];
                int end = critical[(i + 1)%critical.Count];
                while (end <= begin)
                    end += contour.Count;
                curvePoints.Add(contour[begin]);
                int k = (end - begin)/curveStep;
                for (j = 0; j < k; j++)
                {
                    int t = (begin + (end - begin)*(j + 1)/(k + 1))%contour.Count;
                    curvePoints.Add(contour[t]);
                }
                curvePoints.Add(contour[end%contour.Count]);
                path.AddCurve(curvePoints.ToArray());
            }
        }

        public bool Include(IShape shape)
        {
            if (contour != null && contour.Include(shape))
            {
                path = null;
                Page.Changed = true;
                return true;
            }

            return false;
        }

        public bool Exclude(IShape shape)
        {
            if (contour != null && contour.Exclude(shape))
            {
                path = null;
                Page.Changed = true;
                return true;
            }

            return false;
        }

        public string ToXml()
        {
            if (path == null)
                Rebuild();

            StringBuilder sb = new StringBuilder();

            sb.AppendFormat("<path d=\"{0}\"", Svg.PathToString(path));
            sb.Append(Svg.FillToString(fill));
            sb.Append(Svg.StrokeToString(stroke));
            sb.Append(" />\n");

            return sb.ToString();
        }
    }
}
